package tmt.usercenter.web.service;

import com.tmt.annotation.ReflectInvoked;
import com.tmt.datatable.TableRequest;
import com.tmt.datatable.TableResponse;
import com.tmt.datatable.editor.EditorModel;
import com.tmt.datatable.helper.QueryConditionParser;
import com.tmt.helper.DesHelper;
import com.tmt.helper.RandomCodeHelper;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import tmt.usercenter.web.Application;
import tmt.usercenter.web.bean.TmtSemaphore;
import tmt.usercenter.web.controller.app.bean.NewUserInfo;
import tmt.usercenter.web.controller.app.bean.QrRegisterRequest;
import tmt.usercenter.web.controller.app.bean.RealNameAuthInfo;
import tmt.usercenter.web.controller.app.bean.RegisterRequest;
import tmt.usercenter.web.domain.*;
import tmt.usercenter.web.domain.enums.FuncPointType;
import tmt.usercenter.web.domain.enums.RoleItem;
import tmt.usercenter.web.domain.enums.SystemParamCode;
import tmt.usercenter.web.domain.enums.TmtUserType;
import tmt.usercenter.web.domain.security.UserDetailsImpl;
import tmt.usercenter.web.repository.IRealNameMaterialRepo;
import tmt.usercenter.web.repository.ITmtRoleRepo;
import tmt.usercenter.web.repository.ITmtUserRepo;
import tmt.usercenter.web.vo.user.ExistUserInfo;

import javax.persistence.criteria.Predicate;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

@Service
@Transactional(rollbackFor = Exception.class)
public class TmtUserService extends TmtBaseService<TmtUser, Long> {

    public final String USER_CACHE = "tmtUser";

    @Getter
    private ITmtUserRepo iTmtUserRepo;

    private IRealNameMaterialRepo realNameMaterialRepo;

    @Getter
    private TmtUserRoleService userRoleService;

    @Getter
    private PasswordEncoder passwordEncoder;

    private ITmtRoleRepo iTmtRoleRepo;

    private SmHistoryService smHistoryService;

    private SemaphoreService semaphoreService;

    private ClientSystemService clientSystemService;

    private SystemParamService paramService;

    @Autowired
    public TmtUserService(ITmtUserRepo iTmtUserRepo,
                          ITmtRoleRepo iTmtRoleRepo,
                          IRealNameMaterialRepo realNameMaterialRepo,
                          TmtUserRoleService userRoleService,
                          SmHistoryService smHistoryService,
                          SemaphoreService semaphoreService,
                          ClientSystemService clientSystemService,
                          PasswordEncoder passwordEncoder,
                          SystemParamService paramService) {
        super(iTmtUserRepo, Collections.singletonList(userRoleService));
        this.iTmtUserRepo = iTmtUserRepo;
        this.iTmtRoleRepo = iTmtRoleRepo;
        this.realNameMaterialRepo = realNameMaterialRepo;
        this.userRoleService = userRoleService;
        this.passwordEncoder = passwordEncoder;
        this.smHistoryService = smHistoryService;
        this.semaphoreService = semaphoreService;
        this.clientSystemService = clientSystemService;
        this.paramService = paramService;
    }

	/*@Override
	protected synchronized ITmtJpaService getTmtJpaService() {
		if (tmtJpaService == null) {
			DefaultTmtJpaService defaultTmtJpaService = new DefaultTmtJpaService(iTmtUserRepo, new JpaRepository[]{userRoleService.getRepo()});
			tmtJpaService = new DefaultTmtJpaServiceProxy(defaultTmtJpaService);
		}
		return tmtJpaService;
	}*/

    /******************************************************************************
     * 以下为重载部分：主要用于控制增删改查的权限
     ******************************************************************************/


    /*@PreAuthorize("hasAuthority('List_Users')")*/
    @Override
    @Transactional(readOnly = true)
    public Future<TableResponse<TmtUser>> queryByRequest(TableRequest request, Class<TmtUser> clazz,
                                                         QueryConditionParser parser, String[] lazyProperties,
                                                         Map<String, String[]> searchConditions,
                                                         Map<String, String[]> filterConditions,
                                                         Map<String, String[]> limitedConditions) throws ExecutionException, InterruptedException {
        return super.queryByRequest(request, clazz, parser, lazyProperties, searchConditions, filterConditions, limitedConditions);
    }

    @Override
    public void executeTableAction(EditorModel<TmtUser> editorModel, String[] lazyProperties) {
        if (EditorModel.ACTION_CREATE.equals(editorModel.getAction())) {
            editorModel.getData().forEach(x -> x.setPassword(passwordEncoder.encode(x.getPassword())));
        }
        super.executeTableAction(editorModel, lazyProperties);
    }

    @ReflectInvoked
    public TmtUser registerNewUser(TmtUser entity) throws Exception {
        return create(entity);
    }

    @PreAuthorize("hasAuthority('Create_User') or isAnonymous()")
    public TmtUser create(TmtUser entity) throws Exception {
        TmtSemaphore semaphore = semaphoreService.getSemaphore(ClientSystem.class, entity.getOriginSystem().getId());
        semaphore.acquire();
        try {
            /*if (iTmtUserRepo.existsByMobile(entity.getMobile()))
                throw new Exception("手机号码重复");*/

            entity = super.create(entity);
        } finally {
            semaphore.release();
        }
        return entity;
    }

    @PreAuthorize("hasAuthority('Modify_User')")
    public TmtUser update(TmtUser entity) throws Exception {
        TmtSemaphore semaphore = semaphoreService.getSemaphore(ClientSystem.class, entity.getOriginSystem().getId());
        semaphore.acquire();
        try {
            if (iTmtUserRepo.existsByMobileAndIdNot(entity.getMobile(), entity.getId()))
                throw new Exception("手机号码重复");
            entity = super.update(entity);
        } finally {
            semaphore.release();
        }
        return entity;
    }

    @PreAuthorize("hasAuthority('Delete_User')")
    public void delete(TmtUser entity) throws Exception {
        super.delete(entity);
    }

    /**
     * 复位用户密码。
     *
     * @param userId
     * @param rawPassword 原生密码。
     * @return 复位后的新密码。
     */
    public void resetPassword(Long userId, String rawPassword) {
        TmtUser user = iTmtUserRepo.findById(userId);
        if (user != null) {
            user.setPassword(passwordEncoder.encode(rawPassword));
            iTmtUserRepo.save(user);
        }
    }


    /******************************************************************************
     * 以下为业务逻辑方法
     ******************************************************************************/

    @Cacheable(cacheNames = USER_CACHE, key = "'tmtUser_findByName_' + #name", unless = "#result == null")
    @Transactional(readOnly = true)
    @ReflectInvoked("本方法被反射调用了")
    public TmtUser findByUsername(String name) {
        return iTmtUserRepo.findByUsername(name);
    }

    @Transactional(readOnly = true)
    public TmtUser findFirstByMobile(String mobile) {
        return iTmtUserRepo.findFirstByMobile(mobile);
    }

    @Transactional(readOnly = true)
    public Long findIdByUsername(String name) {
        return iTmtUserRepo.findIdByUsername(name);
    }

    @Cacheable(cacheNames = USER_CACHE, key = "'tmtUser_findOriginSystemIdByUsername_' + #name", unless = "#result == null")
    @Transactional(readOnly = true)
    public Long findOriginSystemIdByUsername(String name) {
        return iTmtUserRepo.findOriginSystemIdByUsername(name);
    }

    @Cacheable(cacheNames = USER_CACHE, key = "'tmtUser_findById_' + #id", unless = "#result == null")
    @Transactional(readOnly = true)
    public TmtUser findById(Long id) {
        return iTmtUserRepo.findById(id);
    }

    @Transactional(readOnly = true)
    public String getMobileByUserId(long userId) {
        TmtUser user = iTmtUserRepo.findById(userId);
        return (user != null) ? user.getMobile() : null;
    }

    @Transactional(readOnly = true)
    public boolean existsInClientSystemBy(String clientId, String propName, Object obj, Long idNot) {
        Specification<TmtUser> s = (root, query, cb) -> {
            Predicate p1 = cb.equal(root.get("originSystem").get("clientId"), clientId);
            Predicate p2 = cb.equal(root.get(propName), obj);
            Predicate p3 = idNot == null ? cb.conjunction() : cb.notEqual(root.get("id"), idNot);
            Predicate p = cb.and(p1, p2, p3);
            return query.where(p).getRestriction();
        };
        return !iTmtUserRepo.findAll(s).isEmpty();
    }

    @Transactional(readOnly = true)
    public List<TmtUser> findByOnline(boolean online) {
        return iTmtUserRepo.findByOnline(online);
    }

    @Transactional(readOnly = true)
    public boolean existsByEmail(String email) {
        return iTmtUserRepo.existsByEmail(email);
    }

    @Transactional(readOnly = true)
    public boolean existsByUsername(String username) {
        return iTmtUserRepo.existsByUsername(username);
    }

    @Transactional(readOnly = true)
    public boolean existsByMobile(String mobile) {
        return iTmtUserRepo.existsByMobile(mobile);
    }

    @Transactional(readOnly = true)
    public boolean existsBy(ExistUserInfo existUserInfo) throws Exception {
        TmtUser user = iTmtUserRepo.findById(existUserInfo.getUserId());
        if (user == null)
            throw new Exception("用户不存在");

        return user.getUsername().equals(existUserInfo.getUsername())
                && ((user.getIntroducerCode() != null && user.getIntroducerCode().equals(existUserInfo.getIntroducerCode()))
                || (user.getIntroducerCode() == null && existUserInfo.getIntroducerCode() == null));
    }

    /**
     * 判断是否存在指定用户名和密码的用户
     *
     * @param username    用户名
     * @param rawPassword 未加密的用户密码
     * @return 该用户名是否存在
     */
    @Transactional(readOnly = true)
    public boolean existsByUsernameAndPassword(String username, String rawPassword) {
        TmtUser user = iTmtUserRepo.findByUsername(username);
        return passwordEncoder.matches(rawPassword, user.getPassword());
    }

    /**
     * 更新用户的信息
     *
     * @param user 需要更新的用户
     * @return 更新后的信息
     */
    @Caching(put = {
            @CachePut(cacheNames = USER_CACHE, key = "'tmtUser_findByName_' + #result.username"),
            @CachePut(cacheNames = USER_CACHE, key = "'tmtUser_findById_' + #result.id")
    })
    public TmtUser updateUserInfo(TmtUser user) {
        return iTmtUserRepo.save(user);
    }

    /**
     * 输入的密码和原密码是否匹配
     *
     * @param userPassword 当前用户密码
     * @param pwd          输入的密码
     * @return 是否匹配
     */
    @Transactional(readOnly = true)
    public boolean passwordMatch(String userPassword, String pwd) {
        return passwordEncoder.matches(pwd, userPassword);
    }

    /**
     * 更新密码
     *
     * @param userId 需要更新的用户
     * @param newPwd 新密码
     * @return 更新后的信息
     */
    @Caching(put = {
            @CachePut(cacheNames = USER_CACHE, key = "'tmtUser_findByName_' + #result.username"),
            @CachePut(cacheNames = USER_CACHE, key = "'tmtUser_findById_' + #result.id")
    })
    public TmtUser updatePassword(long userId, String newPwd) {
        TmtUser user = iTmtUserRepo.findById(userId);
        user.setPassword(passwordEncoder.encode(newPwd));
        return iTmtUserRepo.save(user);
    }

    /**
     * 更新当前安全上下文中用户对象的头像信息
     *
     * @param user 最新的用户信息
     */
    @Transactional(readOnly = true)
    public void refreshPrincipal(TmtUser user) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
        if (!Objects.equals(user.getUsername(), userDetails.getUsername())) {
            throw new IllegalArgumentException("不能设置为不同的用户");
        }
        List<TmtRole> allRoles = userRoleService.findRolesByUserId(user.getId());
        userDetails.setFromUser(user, Application.SYSTEM_ID, allRoles);
    }

    /**
     * 更新用户在线状态
     *
     * @param userId
     * @param online
     * @return
     */
    public void updateOnline(Long userId, boolean online) {
        TmtUser user = iTmtUserRepo.findById(userId);
        if (user.isOnline() == online)
            return;

        user.setOnline(online);
        user.setLoginCount(user.getLoginCount() + (online ? 1 : 0));
        iTmtUserRepo.save(user);
    }

    public void updateMobileEnabled(Long userId, String mobile, boolean enabled) {
        TmtUser user = iTmtUserRepo.findById(userId);
        user.setMobile(mobile);
        user.setMobileEnabled(enabled);
        iTmtUserRepo.save(user);
    }

    public void updateEmailEnabled(Long userId, String email, boolean enabled) {
        TmtUser user = iTmtUserRepo.findById(userId);
        user.setEmail(email);
        user.setEmailEnabled(enabled);
        iTmtUserRepo.save(user);
    }

    public UserDetailsImpl getUserDetailsImpl(Long userId, String clientId) {
        TmtUser user = findById(userId);
        return getUserDetailsImpl(user, clientId);
    }

    public UserDetailsImpl getUserDetailsImpl(TmtUser user, String clientId) {
        List<TmtRole> roles = userRoleService.findRolesByUserIdAndClientId(user.getId(), clientId);
        if (roles.size() <= 0) {
            TmtRole role = iTmtRoleRepo.findDefaultRoleBClientId(clientId);
            if (role != null) {
                try {
                    //为用户添加该客户系统的默认角色
                    TmtUserRole userRole = new TmtUserRole();
                    userRole.setRole(role);
                    userRole.setUser(user);
                    userRoleService.create(userRole);
                } catch (Exception ex) {
                }
                roles.add(role);
            }
        }
        return new UserDetailsImpl(user, clientId, roles);
    }


    /**
     * 从手机上注册用户
     *
     * @param registerInfo
     * @return
     */
    public NewUserInfo registerUserFromAppEx(RegisterRequest registerInfo) throws Exception {
        if (registerInfo.getUsername() == null || StringUtils.isEmpty(registerInfo.getUsername().trim())) {
            throw new Exception("输入信息不足：用户账号不能为空");
        }

        if (registerInfo.getPassword() == null || StringUtils.isEmpty(registerInfo.getPassword().trim())
                || registerInfo.getPassword1() == null || StringUtils.isEmpty(registerInfo.getPassword1().trim())) {
            throw new Exception("输入信息不足：账号密码或密码确认不能为空");
        }

        if (!registerInfo.getPassword().trim().equals(registerInfo.getPassword1().trim())) {
            throw new Exception("输入信息异常：账号密码必须与密码确认相同");
        }

        return registerUserFromApp(registerInfo);
    }


    /**
     * 从手机上注册用户
     *
     * @param registerInfo
     * @return
     */
    public NewUserInfo registerUserFromApp(RegisterRequest registerInfo) throws Exception {

        ClientSystem cs = clientSystemService.findByClientId(registerInfo.getClientId());
        if (cs == null)
            throw new Exception("客户端ID无效");

        if (StringUtils.isEmpty(registerInfo.getMobile().trim())
                && StringUtils.isEmpty(registerInfo.getInviteCode().trim())) {
            throw new Exception("输入信息不足：手机号码和邀请码不能都为空");
        }

        if (!StringUtils.isEmpty(registerInfo.getMobile().trim())
                && StringUtils.isEmpty(registerInfo.getCheckCode().trim())) {
            throw new Exception("手机验证码不能为空");
        }

        //先检查手机验证码是否有效。
        SmHistory history = null;
        if (!StringUtils.isEmpty(registerInfo.getMobile().trim())
                && !StringUtils.isEmpty(registerInfo.getCheckCode().trim())) {
            history = smHistoryService.findLastValidByToMobileAndFuncPoint(registerInfo.getMobile(), FuncPointType.USER_REGISTER);
            if (history == null || !history.getContent().contains(registerInfo.getCheckCode()))
                throw new Exception("手机号码或验证码无效");
        }

        //获取邀请人。
        TmtUser introducer = iTmtUserRepo.findFirstByUserType(TmtUserType.SYSADMIN);
        if (!StringUtils.isEmpty(registerInfo.getInviteCode())) {
            try {
                String code = DesHelper.decrypt(registerInfo.getInviteCode());
                Long introducerCode = Long.parseLong(code);
                introducer = iTmtUserRepo.findById(introducerCode);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        if (introducer == null) {
            throw new Exception("邀请码无效");
        }

        NewUserInfo newUserInfo = null;
        TmtSemaphore semaphore = semaphoreService.getSemaphore(ClientSystem.class, cs.getId());
        semaphore.acquire();
        try {
            TmtUser newUser = null;
            String[] password = new String[]{""};
            if (registerInfo.getUsername() == null
                    || StringUtils.isEmpty(registerInfo.getUsername().trim())) {
                newUser = createNewUser(introducer, cs, registerInfo.getMobile(), password);
            } else {
                password[0] = registerInfo.getPassword();
                newUser = createNewUser(registerInfo.getUsername(), registerInfo.getPassword(),
                        registerInfo.getMobile(), introducer, cs);
            }
            //作废当前的验证码
            if (history != null)
                smHistoryService.updateValid(history.getId(), false);

            newUserInfo = new NewUserInfo();
            newUserInfo.setId(newUser.getId());
            newUserInfo.setUsername(newUser.getUsername());
            newUserInfo.setOriginPassword(password[0]);
            newUserInfo.setClientId(cs.getClientId());

            if (introducer.getUserType() != TmtUserType.SYSADMIN) {
                newUserInfo.setInvitor(introducer.getUsername());
            }

        } finally {
            semaphore.release();
        }
        return newUserInfo;
    }

    /**
     * 从手机上扫码注册用户
     *
     * @param registerInfo
     * @return
     */
    public NewUserInfo registerUserFromAppByQrCode(QrRegisterRequest registerInfo) throws Exception {
        Long introducerId = null;
        String clientId = null;
        try {
            introducerId = Long.parseLong(DesHelper.decrypt(registerInfo.getInvitedCode()));
            clientId = DesHelper.decrypt(registerInfo.getEncodedClientId());
        } catch (Exception ex) {
            throw new Exception("二维码无效");
        }

        ClientSystem cs = clientSystemService.findByClientId(clientId);
        if (cs == null)
            throw new Exception("二维码异常:客户端ID无效");

        TmtUser introducer = iTmtUserRepo.findById(introducerId);
        if (introducer == null)
            throw new Exception("二维码异常:邀请人无效");

        NewUserInfo newUserInfo = null;
        TmtSemaphore semaphore = semaphoreService.getSemaphore(ClientSystem.class, cs.getId());
        semaphore.acquire();
        try {
            String[] password = new String[]{""};
            TmtUser newUser = createNewUser(introducer, cs, null, password);

            newUserInfo = new NewUserInfo();
            newUserInfo.setId(newUser.getId());
            newUserInfo.setUsername(newUser.getUsername());
            newUserInfo.setOriginPassword(password[0]);
            newUserInfo.setClientId(clientId);

            if (introducer.getUserType() != TmtUserType.SYSADMIN) {
                newUserInfo.setInvitor(introducer.getUsername());
            }


        } finally {
            semaphore.release();
        }
        return newUserInfo;
    }

    @Transactional(readOnly = true)
    public List<TmtUser> findAllByIntroducerCodeAndOriginSystemClientIdIn(Long userId, List<String> clientIds) {
        return iTmtUserRepo.findAllByIntroducerCodeAndOriginSystemClientIdIn(userId, clientIds);
    }

    public void updateMobile(Long userId, String mobile) {
        TmtUser user = iTmtUserRepo.findById(userId);
        if (user != null) {
            user.setMobile(mobile);
            iTmtUserRepo.save(user);
        }
    }

    public void updateNickName(Long userId, String nickName) {
        TmtUser user = iTmtUserRepo.findById(userId);
        if (user != null) {
            user.setNickname(nickName);
            iTmtUserRepo.save(user);
        }
    }

    public TmtUser updateAvatar(Long userId, String avatar) {
        TmtUser user = iTmtUserRepo.findById(userId);
        if (user != null) {
            user.setAvatar(avatar);
            user = iTmtUserRepo.save(user);
        }
        return user;
    }

    @Transactional(readOnly = true)
    public boolean initialRealNameAuthInfo(long userId, RealNameAuthInfo info) {
        TmtUser user = iTmtUserRepo.findById(userId);
        if (!StringUtils.isEmpty(user.getRealName()) && !StringUtils.isEmpty(user.getIdCardNum())) {
            info.setName(user.getRealName());
            info.setCardNo(user.getIdCardNum());
            info.setCardType(user.getCardType());
            return true;
        }
        return false;
    }

    public void changeIntroducer(long targetUserId, long introducerId) throws Exception {
        TmtUser tmtUser = iTmtUserRepo.findById(targetUserId);
        TmtUser introducer = iTmtUserRepo.findById(introducerId);
        if (tmtUser == null)
            throw new Exception("用户中心目标用户不存在");
        if (introducer == null)
            throw new Exception("用户中心新推荐用户不存在");

        tmtUser.setIntroducerCode(introducer.getId());
        iTmtUserRepo.save(tmtUser);
    }

    /**************************私有方法************************/

    private TmtUser initialUserInfo(TmtUser introducer, String name, String password, String mobile, ClientSystem cs) {
        TmtUser user = new TmtUser();
        user.setOriginSystem(cs);
        user.setUsername(name);
        user.setPassword(passwordEncoder.encode(password));
        user.setEmail(null);
        user.setAvatar("~default_TmtUser_avatar.png");
        user.setEmailEnabled(false);
        user.setEnabled(true);
        user.setOnline(false);

        user.setIntroducerCode(introducer != null ? introducer.getId() : null);

        if (!StringUtils.isEmpty(mobile)) {
            user.setMobile(mobile);
            user.setMobileEnabled(true);
        }

        //给用户赋予前端用户角色。
        List<TmtUserRole> userRoles = new ArrayList<>();
        user.setUserRoles(userRoles);

        List<RoleItem> roles = Collections.singletonList(RoleItem.REG_USER);
        roles.forEach(x -> {
            TmtRole role = iTmtRoleRepo.findByCode(x.name());
            TmtUserRole userRole = new TmtUserRole();
            userRole.setUser(user);
            userRole.setRole(role);
            userRoles.add(userRole);
        });

        return user;
    }

    private TmtUser createNewUser(TmtUser introducer, ClientSystem cs, String mobile, String[] passwordArray) {
        int len = paramService.getSystemParamValue(SystemParamCode.RANDOM_USER_NAME_LEN, int.class);

        String name = null;
        do {
            name = "A" + RandomCodeHelper.getNumberCode(len);
        } while (iTmtUserRepo.existsByUsername(name));

        String password = name.length() <= 4 ? ("a" + name) : ("Aa" + name.substring(name.length() - 4));
        if (passwordArray != null && passwordArray.length >= 1) {
            passwordArray[0] = password;
        }

        TmtUser newUser = initialUserInfo(introducer, name, password, null, cs);
        return iTmtUserRepo.save(newUser);
    }

    private TmtUser createNewUser(String username, String password, String mobile, TmtUser introducer, ClientSystem cs) {
        TmtUser newUser = initialUserInfo(introducer, username.trim(), password.trim(), null, cs);
        return iTmtUserRepo.save(newUser);
    }

    public boolean existsMobileWithExclusion(String mobile, Long excludeUserId) {
        return iTmtUserRepo.existsByMobileAndIdNot(mobile, excludeUserId);
    }
}
